package sys.fsm;

import java.util.*;
import java.io.*;
import java.sql.*;

import sys.database.*;
import sys.transport.*;
import sys.message.*;
import org.json.*;
import sys.utils.*;
import sys.decision.*;

public class Automat
{
    public Automat(int debugLevel, String confFilePath, DecisionSubsystem decision)
    {
        _fsm = new AutomatContext(this);
        timer = new Timer();
        readParams(confFilePath);

        ms = new MessageSenderSMTP(mailserver, portSMTP);

        db = new DBWrapper(DBServer, DB, DBUser, DBPass);
        db.connect();
        
        this.debugLevel = debugLevel;

        n1 = 0; n2 = 0;

        this.IdOnBuyer = 0;
        this.ackByBuyer = 0;
        this.payed = 0;

        this.decision = decision;

        // Uncomment to see debug output.
        _fsm.setDebugFlag(true);
    }

    private void readParams(String confFilePath)
    {
        File confFile = new File(confFilePath);
        String conf = ReadWriteTextFile.getContents(confFile);

        try
        {
            JSONObject jo = new JSONObject(conf);
            n1_max = jo.getInt("n1_max");
            timeout_1_delay = jo.getInt("timeout_1_delay");

            n2_max = jo.getInt("n2_max");
            timeout_2_delay = jo.getInt("timeout_2_delay");

            mailserver = jo.getString("mailserver");
            portSMTP = jo.getString("smtpport");
            sysbMailAddress = jo.getString("sysbMailAddress");
            sysaMailPass = jo.getString("sysbMailPass");

            DBServer = jo.getString("DBServer");
            DB = jo.getString("DB");
            DBUser = jo.getString("DBUser");
            DBPass = jo.getString("DBPass");
        }
        catch(JSONException e)
        {
            System.out.println("Error while parsing config file : " + e.getMessage());
        }
    }

    /*
     * Messages 
     */

    //public void start_Message(long Id, String PSAddress, String userName, int Quantity)
    public void start_Message(long Id, long IdOnBuyer, String PSAddress, String BuyerAddress, String userName, int quantity)
    {
        _fsm.Start(Id, IdOnBuyer, PSAddress, BuyerAddress, userName, quantity);
    }

    public void ack_PayingSystem_1_Message()
    {
        _fsm.Ack_PayingSystem_1_T();
    }

    public void ack_Buyer_1_Message()
    {
        _fsm.Ack_Buyer_1_T();
    }

    public void nack_Buyer_1_Message()
    {
        System.out.println("here");
        _fsm.Nack_Buyer_1_T();
    } 

    /*
     * Entry actions
     */

    public void wait_PayingSystem_1_Entry_A()
    {
        timer.schedule(new Timeout_1(), 0); 
    }

    public void wait_Buyer_1_Entry_A()
    {
        timer.schedule(new Timeout_2(), 0); 
    }

    public void done_Entry_A()
    {
        System.out.println("Before done state. Writing data into 'Statistics' table");

        if (debugLevel == 0)
        {
            try
            {
                ResultSet rs = db.select("Statistics", "Buyer_Address = '" + sysaMailAddress + "' AND User_Name = '" + userName+ "'");
                if (rs.next())
                {
                    int good = rs.getInt("Good"),
                        bad = rs.getInt("Bad");
                    good++;
                    db.update("Statistics", "Good", Integer.toString(good), "Buyer_Address = '" + sysaMailAddress + "' AND User_Name = '" + userName + "'");
                    decision.teach(sysaMailAddress, userName, quantity, (int)((float)(good + bad + 1)/ (float)(bad+1) * 100), 1);
                }
                else
                {
                    db.insert("Statistics", "Buyer_Address, User_Name, Bad, Good", "'" + sysaMailAddress + "','" +  userName + "'," + "0, 1");
                    decision.teach(sysaMailAddress, userName, quantity, 100, 1);
                }
            }
            catch(Exception e)
            {
                System.out.println("Error when updating 'Statistics' table : " + e.getMessage());
            }
        }

        timer.cancel();
    }

    public void cancel_Entry_A()
    {
        System.out.println("Before cancel state. Writing data into 'Statistics' table");

        if (debugLevel == 0)
        {
            try
            {
                ResultSet rs = db.select("Statistics", "Buyer_Address = '" + sysaMailAddress + "' AND User_Name = '" + userName+ "'");
                if (rs.next())
                {
                    int good = rs.getInt("Good"),
                        bad = rs.getInt("Bad");
                    bad++;
                    db.update("Statistics", "Bad", Integer.toString(bad), "Buyer_Address = '" + sysaMailAddress + "' AND User_Name = '" + userName + "'");
                    decision.teach(sysaMailAddress, userName, quantity, (int)((float)(good + bad + 1)/ (float)(bad+1) * 100), 0);
                }
                else
                {
                    db.insert("Statistics", "Buyer_Address, User_Name, Bad, Good", "'" + sysaMailAddress + "','" +  userName + "'," + "1, 0");
                    decision.teach(sysaMailAddress, userName, quantity, 100, 0);
                }
            }
            catch(Exception e)
            {
                System.out.println("Error when updating 'Statistics' table : " + e.getMessage());
            }
        }

        timer.cancel();
    }

    /*
     *  Actions
     */

    public void start_A(long Id, long IdOnBuyer, String PSAddress, String BuyerAddress, String userName, int quantity)
    {
        System.out.println("New request from buyer. Starting timer.");

        if (debugLevel == 0)
        {
            this.Id = Id;
            this.IdOnBuyer = IdOnBuyer;
            this.PSAddress = PSAddress;
            this.sysaMailAddress = BuyerAddress;
            this.quantity = quantity;
            this.userName = userName;
            db.insert("Request", "ID, IDOnBuyer, PayingSystem_Address, Buyer_Address, User_Name, Quantity", Id + "," +  IdOnBuyer + ",'" + PSAddress + "','" + BuyerAddress + "','" + userName + "'," + quantity);
        }
    }

    public void ack_PayingSystem_1_A()
    {
        System.out.println("Acknowledged by paying system");
        
        if (debugLevel == 0)
        {
            this.payed = 1;
            db.update("Request", "Payed", "1", "Id = " + Id);
        }
    }

    /*
     * Request ack from paying system
     */
    public void timeout_1_A()
    {
        System.out.println("In timeout1. N = " + n1);

        if (debugLevel == 0)
        {
            try
            {
                JSONMessage mes = new JSONMessage(sysbMailAddress, PSAddress, "Is request payed?");
                mes.put("ID", Id);
                mes.put("Type", "payingStatus");
                mes.put("AskAboutID", IdOnBuyer);
                ms.send(mes);
            }
            catch(Exception e)
            {
                System.out.println("Error while making JSONMessage or sending message in temeout_1_A()");
            }
        }

        n1++;
        timer.schedule(new Timeout_1(), timeout_1_delay); 
    }
    
    /*
     * Buyer've granted ack
     */
    public void ack_Buyer_1_A()
    {
        System.out.println("Acknowledged by buyer");

        if (debugLevel == 0)
        {
            this.ackByBuyer = 1;
            db.update("Request", "AckByBuyer", "1", "Id = " + Id);
        }
    }

    /*
     * Buyer nack
     */
    public void nack_Buyer_1_A()
    {
        System.out.println("Nack by buyer");
    }
    /**
     * Ack on ack from buyer
     */
    public void timeout_2_A()
    {
        System.out.println("In timeout1. N = " + n2);

        if (debugLevel == 0)
        {
            try
            {
                JSONMessage mes = new JSONMessage(sysbMailAddress, sysaMailAddress, "Ack on Ack from Buyer");
                mes.put("Type", "ackSeller2");
                mes.put("ackOn", IdOnBuyer);
                ms.send(mes);
            }
            catch(Exception e)
            {
                System.out.println("Error while making JSONMessage or sending message in temeout_1_A()");
            }
        }

        n2++;
        timer.schedule(new Timeout_2(), timeout_2_delay); 
    }
 
    /*
     * Guard conditions
     */

    public boolean checkTimeout_1_Times()
    {
        if (n1 < n1_max) 
            return true;
        else
            return false;
    } 

    public boolean checkTimeout_2_Times()
    {
        if (n2 < n2_max) 
            return true;
        else
            return false;
    }   
  
    /*
     * Timer event handlers
     */ 
        
    class Timeout_1 extends TimerTask
    {
        public void run()
        {
            try
            {
                _fsm.Timeout_1_T();
            }
            catch(Exception e)
            {
                System.out.println("Timeout1 tryed to call illegal transition. Do nothing.");
            }
        }
    }

    class Timeout_2 extends TimerTask
    {
        public void run()
        {
            try
            {
                _fsm.Timeout_2_T();
            }
            catch(Exception e)
            {
                System.out.println("Timeout2 tryed to call illegal transition. Do nothing.");
            }
        }
    }

    // Request parametrs
    private String PSAddress;
    private String userName;
    private long Id, IdOnBuyer;
    private int quantity;
    private int payed, ackByBuyer;

    // Configs
    private String mailserver, sysaMailAddress, sysbMailAddress, sysaMailPass, portSMTP;
    private String DBServer, DB, DBUser, DBPass;
    private int n1, n1_max, n2, n2_max;
    private long timeout_1_delay, timeout_2_delay;
    private int debugLevel;

    private AutomatContext _fsm;
    private Timer timer;
    private DBWrapper db;
    private MessageSender ms;
    DecisionSubsystem decision;
}
